패키지 매니저 알아보기 | npm, yarn, pnpm 그리고 corepack 까지
2023. 11. 24.
#개발환경
패키지 매니저의 발전
- npm
- yarn classic
- yarn berry
- pnpm
사전 지식
Phantom Dependency
- 💡 npm 과 yarn classic 모두 node_modules 가 flat 한 형태의 구조이기 때문에 내가 설치하지 않은 의존성을 사용할 수 있음
- 왜냐하면 A 패키지를 설치했는데 A 패키지의 의존성 패키지가 B 가 있는 경우 B 도 node_modules 바로 밑에 (A 과 동일한 레벨 위치) 평평한 구조로 생기기 때문 (v3 이미지)
⇒ 이런 현상으로 많은 위험이 있을 수 있음
ex) App 에서 A 패키지 설치했는데 B 도 사용할 수 있음
- 팬텀 디펜더시 문제 사례
- a 패키지에 axios 설치
- a 에서 설치한 axios 는 root 의 node_modules 에 설치됨
- b 에서 axios 를 따로 설치하지 않고 axios 사용
- require(”axios”) 로 axios 패키지 가져옴
package-b
에 axios 가 없음 → 상위로 이동root
에서 axios 찾음 → axios 있음! → 그래서 접근 가능- a 에서 axios 사용하지 않는다고 판다해서 axios 제거
- b 에서 axios 사용하는 구문에서 터짐
npm install -w
이때 b 에서 axios 패키지에 접근이 가능함
왜냐하면
⇒ 즉 b 는 a 의 변경 사항으로 인해 영향을 받음! 안티패턴!!!
node_modules 의 패키지에 접근하는 구조
- npm 와 같은 패키지 매니저에서
package.json
에서 프로젝트 관련 정보를 관리하고node_modules
폴더 안에 사용 가능한 패키지가 존재- 소스코드에서
require
또는import
로 패키지에 접근하면 - 상대 경로인 경우 해당 파일의 위치를 찾고
- 그냥 이름만 적혀 있는 경우, 현재 위치의 node_modules 를 찾아보고 없으면 상위 폴더로 이동하면서 … 찾음!
즉require
,import
와 같이 코드로 패키지에 접근하면 해당 위치에서 node_moduels 찾고 없으면 상위 폴더에서 node_moduels 찾고 없으면 …. 계속 반복해서 해당 패키지를 찾을려고 함
corepack
- corepack 은 최근에 npm(NodeJS) 에서 추가된 기능으로 nodejs 설치하면 corepack 도 같이 설치됨
- corepack 은 yarn 개발자가 추가한 기능으로 package.json 에서 어떤 패캐지 매니저의 버전을 명시해서 관리하는 것으로 이로 인해 해당 프로젝트는 해당 패키지 매니저의 버전을 사용할 수 있게 자동으로 해주는 기능!
## 프로젝트 초기화후 corepack 활성화 필요 mkdir root-project cd root-project corepack enable ## -2 는 yarn-berry 버전으로 초기화 yarn init -2 -w ## 이미 있는 경우? # yarn set version berry
{ "name": "root-project", "packageManager": "yarn@4.0.1", "workspaces": [ "packages/*" ] }
- 해당 프로젝트는 yarn 의 4 버전을 사용한다는 의미
npm
- NodeJS 를 설치하면 기본 설치된 npm
- npm v2 는
- 중첩된 구조로 의존성 관리 하지만, 해당 방식으로 인해 이슈 발생
- windows 의 경우 폴더의 길이가 제한이 있어 중첩되서 많이 들어가면 폴더명이 겹치는 에러 발생
- A 사람이 패키지를 설치하고 (같은 환경에서) B 도 똑같이 설치했는데 잘 설치되지 않는 이슈 발생
⇒ 그래서 lock 파일 도입함 (첫 시작은 yarn 에서 시작)
- npm v3 은
- node_modules에서 flat 한 구조로 관리
yarn classic
- yarn 의 첫번째 방식으로 npm 의 동작 방식을 그대로 가져오면서 아쉬운 부분들을 해결하고자 Facebook, Google 이 공동 개발
- Lock 파일 개념 도입해서 일관적으로 의존성을 재설치할 수 있도록 함
- 보안 향상
- ✨ 병렬 설치로 성능 향상
- 워크스페이스 개념 도입
- 후에 npm 도 워크스페이스 개념 도입
⇒ 앞서 npm v2 의 문제점이 같은 환경에서 A, B 가 각각 설치했는데 잘 설치되지 않는 이슈 보완
pnpm
- 기존 패키지 매니저 (npm, yarn) 을 개선하고자 어떤 개발자가 pnpm 출시
- ✨ 하드링크와 소프트 링크를 적절히 사용해서 성능 향상과 디스크 효율성을 강조
- npm v3 의 flat 한 구조를 버리고 중첩구조를 유지하면서 문제를 해결
- flat 한 구조는 팬텀 디펜더시 문제를 해결
- 중첩구조를 유지하며서 문제 해결은 일관성 있는 설치(lock 파일) 및 속도 개선 등
- 워크스페이스 기능 제공
yarn berry
- yarn classic 과 다른 완전 새로운 버전 (2020 출시)
- node_modules 를 사용하지 않고 압축파일을 사용하는 (plug in play - pnp) 방식 사용
- 압축파일을 사용해서 설치 시간을 최소화하고 설치하지 않아도 사용 가능한 개념(zero-install) 도입
하지만 완전 zero-install 은 아님
- (node_modules 방식이 아니기 때문에) 엄연한 의존 관계로 팬텀 디펜더시와 같은 문제가 발생하지 않음
- 워크스페이스 제공
패키지 매니저와 workspace
패키지를 가져오는 방식
- 외부에서 가져오는 방식
npm i ...
- 내부에서 가져오는 방식
npm link ...
npm link
위와 같은 구조를 구성한다고 하면
# root mkdir project-root cd project-root npm init -y mkdir package-a cd package-a npm link ./package-a cd .. mkdir package-b cd package-b npm link ./package-b
참고
- root 의 main.js 실행
- require 만나서
현재 파일의 위치의 node_modules
에서 package-b 폴더 가 있는지 찾음
- 있으니깐
node_moduels/package-b
안에 있는 package.json 파일을 찾음
node_moduels/package-b
의 package.json 파일에서 main 을 찾아서 정의된 파일 위치(index.js) 을 가져옴
npm-workspace
# root mkdir project-root cd project-root npm init -y ## 워크스페이스 추가 ## -w 는 워크스페이스 의미 npm init -y -w ./packages/a npm init -y -w ./packages/b
# root project-root 의 package.json { "name": "project-root", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "workspaces": [ "packages/a", "packages/b" ] }
추가 명령어
## 워크스페이스 생성 npm init -y -w ./packages/a # --workspace 워크스페이스를 의미 ➜ npm start --workspace a # --workspace 의 단축형 -w ➜ npm start -w a # --worksacpes 여러 워크스페이들을 의미 ➜ npm start --workspaces # --workspaces 의 단축형 -ws ➜ npm start -ws # --if-present 는 해당 스크립트가 있는 경우 수행 ➜ npm start -ws --if-present # --include-workspace-root 는 root 의 스크립트 ➜ npm start -ws --include-workspace-root # --if-present --include-workspace-root root 가 있을때만 수행 ➜ npm start -ws --if-present --include-workspace-root # 직접 의존하고 있는 관계들만 표현 ➜ npm ls
yarn-classic-workspace
- yarn 1.0 부터 사용 가능
- yarn 1버전대는 private 속성을 true 로 해야함
- yarn link 제공 (npm 과 동일)
- package.json 의 workspaces 속성을 통해 정의 (npm 과 동일)
- 프로젝트 내의 모든 패키지의 의존성이 함께 설치되고(lock 파일) 관리되어 충돌이 적고 최적화에 유리
# root mkdir project-root cd project-root yarn init -y -p # private 모드로 init ## 하위 패키지 생성 mkdir packages packages/a packages/b cd package/a && yarn init -y cd .. # 루트로 다시 이동 cd package/b && yarn init -y
{ "name": "project-root", "version": "1.0.0", "main": "index.js", "license": "MIT", "private": true, ## 추가 "workspaces": [ "packages/*" ] }
# 실행 yarn # workspace 에 외부 의존성 추가 yarn workspace a add axios # workspace 스크립트 수행 yarn workspace a start # 여러 workspace 스크립트 수행 yarn workspaces run start # workspace 의존 관계 yarn workspaces info
yarn-berry-workspace
- yarn 의 2번째 버전
- 💡 기본적으로 명시적인 의존 관계를 나타내야 사용 가능 (yarn-classic 과 차이점)
- npm 과 yarn-classic 은 팬텀 디펜더시 문제로 각 패키지들이 root 의 node_modules 에 flat 한 형태로 있기 때문에 모든 패키지에서 사용 가능한 문제가 있음
하지만, yarn-berry 는 명시적으로 의존 관계를 나타내야 사용할 수 있음
- 💡 node_modules 에 패키지를 저장하는 방식이 아닌 패키지를 압축해서 한 개의 파일을
.yarn/cache
폴더에 수평적으로 저장 - 수평적으로 존재하기 때문에 빠르게 찾을 수 있음
- 압축파일을 설치하기 때문에 파일 개수가 감소하여 설치가 빠름
- 팬텀 디펜더시가 발생하지 않음
⇒ Plug’n’Play (PnP) 방식
Zero Install 을 이용하여 저장소에서 함께 관리 (압축 파일을 git에 함께 올려서 관리)
내가 직접 설치하지 않은 패키지를 사용 가능한 것!
- 단점
- Zero Install 이라고 해서 아무것도 설치하지는 않음
- PnP 방식으로 압축파일을 관리하기 때문에 저장소 자체가 커질 수 있음
- 패캐지들을 압축 파일로 관리하기 때문에 사용하기 위한 IDE 추가 설정 필요
IDE 에서 직접 사용하는 많은 도구(ESLint, Typescript, Prettier 등) 들을 SDK 를 통해 우회 호출할 수 있도록 추가 설정 필요
# install 후 IDE 에 인식되게하려면 아래 명령어 수행 yarn dlx @yarnpkg/sdks vscode
yarn-classic 과 의존성 추가하는 것은 같음
pnpm
- 빠르고, 효율적인 패키지 매니저
- 모노레포 지원하고 , flat 하지 않은 node_modules 방식으로 엄격한 의존관리가 가능
→ 엄격한 의존 관리 == 팬텀 디펜던시 문제 해결
- 시스템 내에 단일 패키지 스토어에 모든 의존성을 보관하기 때문에 디스크 공간이 절약됨
- 필요한 의존성을 식별해서 스토어로 가져오고 (기존에 설치되지 않은 것만 설치), 디렉터리 구조를 계산해서 하드 링크하는 과정을 가지기 때문에 설치 속도가 빠름
⇒ 필요한 패키지들을 스토어에서 관리하고 각각의 프로젝트 에서 필요한 패키지를 설치하면 store 에서 하드 링크로 가져오는 방식이 때문에 설치 속도가 빠름
🧐 최초 설치시에는 store 에 저장후 하드링크하지만, store 에 있는 경우는 하드링크이기 때문에 빠르다는거 같음
🧐 하드링크로 해서 첫번째만 외부에서 다운받고 그 후 부터 스토어에 있는 거면 외부에서 다운이 아닌 복/붙 느낌이라서 빠르다고 하는건가??
- pnpm 은 symlink 를 사용해서 프로젝트의 직접적인 의존성만을 모듈 디렉터리의 루트로 추가함
- 각 패키지에서 사용하는 의존성들은 패키지 내부에서 관리를 하고 (공통적인 모듈로 올리지 않음) 각 패키지의 모듈들을 symlink 로 링크되어 관리함.
⇒ 💡 이런 구조로 관리하기 때문에 팬텀 디펜더시 문제를 해결했고, 엄격하고 직접적인 의존성 구조를 가지고 있음
# root mkdir project-root cd project-root pnpm init corepack use pnpm@8.10.0 # corepack 설정 ## 하위 패키지 생성 mkdir packages packages/a packages/b cd package/a && pnpm init cd .. # 루트로 다시 이동 cd package/b && pnpm init
# pnpm-workspace.yaml packages: - "packages/*"
- pnpm 의 경우 workspace 를 package.json 의 workspace 속성이 아닌
pnpm-workspace.yaml
파일로 관리!의존관계 추가
{ "name": "a", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "axios": "^1.6.0", "b": "workspace:*" } }
명령어 참고
# 특정 패키지에 의존성 설치 pnpm --filter a add axios # 특정 패키지 의존성 제거 pnpm --filter a remove axios # 스크립트 실행 pnpm --filter a start ## -r 옵션은 run 의 단축 pnpm -r run start # root 스크립트 수행? pnpm -r --include-workspace-root start # 의존 관계 pnpm -r list # pnpm store 의 경로를 변경하는 명령어 # pnpm config get store-dir pnpm config set store-dir ~/.pnpm-store